// 
// SoapTrace.Net - A soap message monitoring utility. 
// 
// Copyright (C) 2004 Matthew Ward
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 
// Matthew Ward (mrward@users.sourceforge.net)
// 

using SoapTrace.Core.Communication;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace SoapTrace.Core.Formatters
{
	/// <summary>
	/// A class that formats soap messages for display.
	/// </summary>
	public class SoapMessageFormatter : IMessageFormatter, IDisposable
	{
		/// <summary>
		/// Disposed flag.
		/// </summary>
		private bool m_Disposed;
		
		/// <summary>
		/// The web services that are configured and known to this 
		/// message formatter.
		/// </summary>
		private WebServiceCollection m_WebServices;

		/// <summary>
		/// The configuration for this formatter.
		/// </summary>
		private SoapMessageFormatterConfig m_Config = 
			new SoapMessageFormatterConfig( );

		/// <summary>
		/// The configuration editor for this formatter object.
		/// </summary>
		private FormatterConfigurationEditor m_ConfigEditor;

		/// <summary>
		/// Creates a new instance of the <see cref="SoapMessageFormatter"/>
		/// class.
		/// </summary>
		public SoapMessageFormatter( )
		{
			m_Config = SoapMessageFormatterConfig.Load( );

			m_WebServices = m_Config.WebServices;

			m_ConfigEditor = new FormatterConfigurationEditor( m_Config, this );
		}

		/// <summary>
		/// Disposes this class.
		/// </summary>
		public void Dispose( )
		{
			Dispose( true );

			GC.SuppressFinalize( this );
		}
		
		/// <summary>
		/// Creates a new instance of the <see cref="SoapMessageFormatter"/>
		/// class.
		/// </summary>
		/// <param name="webServices">A collection of web services
		/// to use as configuration for this formatter.</param>
		public SoapMessageFormatter( WebServiceCollection webServices )
		{
			m_WebServices = webServices;
		}

		/// <summary>
		/// Gets or sets the web services associated with this formatter.
		/// </summary>
		public WebServiceCollection WebServices
		{
			[ DebuggerStepThrough ]
			get
			{
				return m_WebServices;
			}
		}

		/// <summary>
		/// Gets the name of this message formatter.
		/// </summary>
		public string Name
		{
			[ DebuggerStepThrough ]
			get
			{
				return "Formatted Soap Parameters";
			}
		}

		/// <summary>
		/// Formats the soap message request.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The uri of the request.</param>
		/// <param name="webServiceName">The web service name.</param>
		/// <param name="webMethod">The web service operation.</param>
		/// <param name="message">The soap message request.</param>
		/// <returns>A formatted soap message request.</returns>
		public string FormatRequestText( string server,
			string uri,
			string webServiceName,
			string webMethod, 
			ISoapMessage message )
		{
			// Get the parameter value formatter.

			ParameterValueFormatter Formatter = null;

			WebServiceOperation WebServiceOperation = null;

			if( FindWebServiceOperation( out WebServiceOperation, 
				uri, 
				webServiceName, 
				webMethod ) )
			{
				Formatter = new ParameterValueFormatter( WebServiceOperation.Parameters );
			}
			else
			{
				Formatter = new ParameterValueFormatter( );
			}

			// Format the soap request.

			return FormatRequest( Formatter, message.Message );
		}

		/// <summary>
		/// Formats the soap message response.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The uri of the response.</param>
		/// <param name="webServiceName">The web service name.</param>
		/// <param name="webMethod">The web service operation.</param>
		/// <param name="message">The soap message response.</param>
		/// <returns>A formatted soap message response.</returns>
		public string FormatResponseText( string server,
			string uri,
			string webServiceName,
			string webMethod, 
			ISoapMessage message )
		{
			// Get the return value formatter.

			ReturnValueFormatter Formatter = null;

			WebServiceOperation WebServiceOperation = null;

			if( FindWebServiceOperation( out WebServiceOperation, 
				uri, 
				webServiceName, 
				webMethod ) )
			{
				Formatter = new ReturnValueFormatter( WebServiceOperation.ReturnValue );
			}
			else
			{
				Formatter = new ReturnValueFormatter( );
			}

			// Format the soap response.

			return FormatResponse( Formatter, message.Message );
		}

		/// <summary>
		/// Formats the soap message request.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The uri of the request.</param>
		/// <param name="webServiceName">The web service name.</param>
		/// <param name="webMethod">The web service operation.</param>
		/// <param name="message">The soap message request.</param>
		/// <returns>A formatted soap message request.</returns>
		public FormattedText FormatRequest( string server,
			string uri,
			string webServiceName, 
			string webMethod, 
			ISoapMessage message )
		{
			FormattedText FormattedText = new FormattedText( Color.Black );
			FormattedText.Append( FormatRequestText( server, uri, webServiceName, webMethod, message ) );
			return FormattedText;
		}

		/// <summary>
		/// Formats the soap message response.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The uri of the response.</param>
		/// <param name="webServiceName">The web service name.</param>
		/// <param name="webMethod">The web service operation.</param>
		/// <param name="message">The soap message response.</param>
		/// <returns>A formatted soap message response.</returns>
		public FormattedText FormatResponse( string server,
			string uri,
			string webServiceName,
			string webMethod, 
			ISoapMessage message )
		{
			FormattedText FormattedText = new FormattedText( Color.Black );
			FormattedText.Append( FormatResponseText( server, uri, webServiceName, webMethod, message ) );
			return FormattedText;
		}
		
		/// <summary>
		/// Returns whether the message formatter is configurable.
		/// </summary>
		public bool IsConfigurable
		{
			[ DebuggerStepThrough ]
			get
			{
				return true;
			}
		}

		/// <summary>
		/// Returns the configuration editor object for this class.
		/// </summary>
		public IConfigurationEditor ConfigurationEditor
		{
			[ DebuggerStepThrough ]
			get
			{
				return m_ConfigEditor;
			}
		}

		/// <summary>
		/// Formats the soap response message
		/// </summary>
		/// <param name="formatter">The formatter to use.</param>
		/// <param name="message">The soap response message.</param>
		/// <returns>A formatted soap response message.</returns>
		private static string FormatResponse( ReturnValueFormatter formatter, string message )
		{
			XmlDocument Document = new XmlDocument( );
			Document.LoadXml( message );

			XmlNamespaceManager NamespaceManager = 
				new XmlNamespaceManager( Document.NameTable );

			NamespaceManager.AddNamespace( "soap", 
				"http://schemas.xmlsoap.org/soap/envelope/" );

			XmlElement SoapBody = 
				( XmlElement )Document.SelectSingleNode( "//soap:Body", NamespaceManager );

			StringBuilder Text = new StringBuilder( );
			
			foreach( XmlNode SoapMethodNode in SoapBody.ChildNodes )
			{
				Text.Append( String.Concat( SoapMethodNode.Name, "\r\n\r\n" ) );

				int ReturnValueIndex = 0;

				foreach( XmlNode SoapParameterNode in SoapMethodNode.ChildNodes )
				{
					if( SoapParameterNode.ChildNodes.Count == 0 )
					{
						Text.Append( formatter.Format( ReturnValueIndex, 
							SoapParameterNode.Name, 
							SoapParameterNode.InnerText ) );

						++ReturnValueIndex;
					}
					else
					{
						foreach( XmlNode ArrayParameterNode in SoapParameterNode.ChildNodes )
						{
							if( ArrayParameterNode.NodeType == XmlNodeType.Element )
							{
								Text.Append( formatter.Format( ReturnValueIndex, 
									ArrayParameterNode.Name, 
									ArrayParameterNode.InnerText ) );
							}
							else if( ArrayParameterNode.NodeType == XmlNodeType.Text )
							{
								Text.Append( formatter.Format( ReturnValueIndex, 
									SoapParameterNode.Name, 
									ArrayParameterNode.InnerText ) );
							}

							++ReturnValueIndex;
						}
					}
				}
			}

			return Text.ToString( );
		}

		/// <summary>
		/// Formats the soap request message.
		/// </summary>
		/// <param name="formatter">The parameter value formatter to use
		/// when formatting parameters.</param>
		/// <param name="message">The soap request message.</param>
		/// <returns>A formatted soap request message.</returns>
		private static string FormatRequest( ParameterValueFormatter formatter, string message )
		{
			XmlDocument Document = new XmlDocument( );
			Document.LoadXml( message );

			XmlNamespaceManager NamespaceManager = 
				new XmlNamespaceManager( Document.NameTable );

			NamespaceManager.AddNamespace( "soap", 
				"http://schemas.xmlsoap.org/soap/envelope/" );

			XmlElement SoapBody = 
				( XmlElement )Document.SelectSingleNode( "//soap:Body", NamespaceManager );

			StringBuilder Text = new StringBuilder( );
			
			foreach( XmlNode SoapMethodNode in SoapBody.ChildNodes )
			{
				Text.Append( String.Concat( SoapMethodNode.Name, "\r\n\r\n" ) );

				for( int i = 0; i < SoapMethodNode.ChildNodes.Count; ++i )
				{
					XmlNode SoapParameterNode = SoapMethodNode.ChildNodes[ i ];	
					
					if( SoapParameterNode.ChildNodes.Count == 0 )
					{
						Text.Append( formatter.Format( i, 
							SoapParameterNode.Name, 
							SoapParameterNode.InnerText ) );
					}
					else
					{
						for( int ArrayIndex = 0; ArrayIndex < SoapParameterNode.ChildNodes.Count; ++ArrayIndex )
						{
							XmlNode ArrayParameterNode = SoapParameterNode.ChildNodes[ ArrayIndex ];
							
							if( ArrayParameterNode.NodeType == XmlNodeType.Element )
							{
								Text.Append( formatter.Format( i, 
								    ArrayIndex,
									ArrayParameterNode.Name, 
									ArrayParameterNode.InnerText ) );
							}
							else if( ArrayParameterNode.NodeType == XmlNodeType.Text )
							{
								Text.Append( formatter.Format( i, 
									SoapParameterNode.Name, 
									SoapParameterNode.InnerText ) );
							}
						}
					}					
				}				
			}

			return Text.ToString( );
		}

		/// <summary>
		/// Find the web service operation for a web service.
		/// </summary>
		/// <param name="operation">The matching web service operation object.</param>
		/// <param name="operationName">The name of the web service operation to 
		/// find.</param>
		/// <param name="uri">The web service uri.</param>
		/// <param name="webServiceName">The web service name.</param>
		/// <returns>A flag indicating whether a web service operation was
		/// found to match the given name.</returns>
		private bool FindWebServiceOperation( out WebServiceOperation operation, 
			string uri, 
			string webServiceName, 
			string operationName )
		{
			bool Found = false;
			operation = null;

			WebService WebService = m_WebServices.Find( webServiceName );

			if( WebService == null )
			{
				WebService = m_WebServices.FindByUri( uri );
			}

			if( WebService != null )
			{
				WebServiceOperation MatchedOperation = WebService.Operations.Find( operationName );

				if( MatchedOperation != null )
				{
					Found = true;
					operation = MatchedOperation;
				}
			}

			return Found;
		}
		
		/// <summary>
		/// Disposes managed and unmanaged objects. 
		/// </summary>
		/// <param name="disposing">If <see langword="true"/> the method has been called directly
		/// or indirectly by a user's code. Managed and unmanaged resources
		/// can be disposed.
		/// If disposing equals <see langword="false"/>, the method has been called by the 
		/// runtime from inside the finalizer and you should not reference 
		/// other objects. Only unmanaged resources can be disposed.
		/// </param>
		private void Dispose( bool disposing )
		{
			if( !m_Disposed )
			{
				if( disposing )
				{
					// Dispose managed resources.

					if( m_ConfigEditor != null )
					{
						m_ConfigEditor.Dispose( );
					}
				}
	             
				// Clean up unmanaged resources here.
 
			}

			m_Disposed = true;         
		}		
	}
}
